from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client
from django.conf import settings

class FastDFSStorage(Storage):
    """自定义文件存储系统:实现文件被转存到fdfs"""

    def __init__(self, client_conf=None, base_url=None):
        """构造方法,
        当存在存储数据时,django会自动的调用构造方法,
        但不会传参进来
        """
        self.client_conf = client_conf or settings.FDFS_CLIENT_CONF
        self.base_url = base_url or settings.FDFS_BASE_URL

    def _open(self, name, mode="rb"):
        """打开文件时调用的
        因为此时只有文件的上传,没有文件要打开,但是又必须要实现,所以pass
        """
        pass

    def _save(self, name, content):
        """
        存储文件时调用的
        :param name: 要存储的文件的名字
        :param content: 要存储的文件的对象,File类型的对象,需要调用read()读取出要上传的文件的内容
        :return: file_id
        """
        # """FastFDS工作流程:
        # client给tracker发送上传请求,tracker把请求发送storage问它有没有地方存;
        # 如果有地方存,storage把存储地址ip和端口号返回给tracker,tracker再返回给client;
        # client拿到这个ip和端口号后,再把文件上传到指定ip地址.
        # """
        # """client工作流程分析:
        # client共做了两件事:
        #     1.向tracker发送上传请求,并得到ip地址和端口号.
        #     2.向storage上传文件,并返回上传结果.
        # """

        # 创建fdfs客户端,跟tracker进行交互
        client = Fdfs_client(self.client_conf)
        # 调用上传方法:根据文件的内容上传的,网络中只能以二进制传输
        ret = client.upload_by_buffer(content.read())
        # 判断上传是否成功
        if ret.get("Status") != "Upload successed.":
            raise Exception("upload file failed")

        # 返回file_id:模型类会自动的读取这个file_id,存储到模型类中的ImageField字段
        file_id = ret.get("Remote file_id")
        return file_id

    def exists(self, name):
        """
        判断文件是否已经在本地存储
        返回True表示文件已经存储在本地,django不会再去存储该文件
        返回False时,告知Django该文件是新的文件,请你存储.
        只有返回False,Django才会积极的存储文件到fdfs
        :param name: 要上传的文件的名字
        :return: False
        """
        return False

    def url(self, name):
        """
        返回文件的全路径
        该方法是提供给模型类中ImageField字段对应的属性调用的(file_id被存储在image字段中)
        :param name: 要返回全路径的文件的名字
        :return:
        """
        # 返回storage的ip和端口,文件名字
        return self.base_url + name
    """
    Q:_save方法已经返回了file_id,为什么还要定义url方法返回文件名字?
      有什么办法可以从_save方法中获取file_id,这样就不用定义url方法了?
    W:获取文件名字必须定义url方法.
      因为file_id被存储在ImageField字段中,url可以返回一个包含文件名字的全路径,
      取到全路径就可以把图片渲染出来.
      调取全路径方法为image.url
    """